package drds.binlog.server.netty.handler;

import com.google.protobuf.ByteString;
import com.google.protobuf.CodedOutputStream;
import com.google.protobuf.WireFormat;
import drds.binlog.common.position.ClientInfo;
import drds.binlog.common.zookeeper.running.Monitor;
import drds.binlog.common.zookeeper.running.ServerRunningMonitors;
import drds.binlog.protocol.Message;
import drds.binlog.protocol.entry_protocol_proto.Entry;
import drds.binlog.protocol.protocol_proto.*;
import drds.binlog.server.embedded.EmbeddedServer;
import drds.binlog.server.netty.Nettys;
import drds.binlog.server.netty.listener.ChannelFutureAggregator;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.slf4j.helpers.MessageFormatter;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.concurrent.TimeUnit;


public class SessionHandler extends SimpleChannelHandler
{

    private static final Logger logger = LoggerFactory.getLogger(SessionHandler.class);
    private EmbeddedServer embeddedServer;

    public SessionHandler()
    {
    }

    public SessionHandler(EmbeddedServer embeddedServer)
    {
        this.embeddedServer = embeddedServer;
    }

    public void messageReceived(ChannelHandlerContext channelHandlerContext, MessageEvent messageEvent) throws Exception
    {
        logger.info("message receives in session handler...");
        long start = System.nanoTime();
        ChannelBuffer channelBuffer = (ChannelBuffer) messageEvent.getMessage();
        Packet packet = Packet.parseFrom(channelBuffer.readBytes(channelBuffer.readableBytes()).array());
        ClientInfo clientInfo = null;
        try
        {
            switch (packet.getType())
            {
                case subscription:
                    Sub sub = Sub.parseFrom(packet.getBody());
                    if (StringUtils.isNotEmpty(sub.getDestination()) && StringUtils.isNotEmpty(sub.getClientId()))
                    {
                        clientInfo = new ClientInfo(sub.getDestination(),
                                Short.valueOf(sub.getClientId()),
                                sub.getFilter());
                        MDC.put("destination", clientInfo.getDestination());

                        // 尝试启动，如果已经启动，忽略
                        if (!embeddedServer.isStart(clientInfo.getDestination()))
                        {
                            Monitor runningMonitor = ServerRunningMonitors.getRunningMonitor(clientInfo.getDestination());
                            if (!runningMonitor.isRunning())
                            {
                                runningMonitor.start();
                            }
                        }

                        embeddedServer.subscribe(clientInfo);
                        // ctx.setAttachment(clientInfo);// 设置状态数据
                        byte[] ackBytes = Nettys.ackPacket();
                        Nettys.write(channelHandlerContext.getChannel(), ackBytes, new ChannelFutureAggregator(sub.getDestination(),
                                sub, packet.getType(), ackBytes.length, System.nanoTime() - start));
                    } else
                    {
                        byte[] errorBytes = Nettys.errorPacket(401, MessageFormatter.format("destination or clientId is null", sub.toString()).getMessage());
                        Nettys.write(channelHandlerContext.getChannel(), errorBytes, new ChannelFutureAggregator(sub.getDestination(),
                                sub, packet.getType(), errorBytes.length, System.nanoTime() - start, (short) 401));
                    }
                    break;
                case unsubscription:
                    Unsub unsub = Unsub.parseFrom(packet.getBody());
                    if (StringUtils.isNotEmpty(unsub.getDestination()) && StringUtils.isNotEmpty(unsub.getClientId()))
                    {
                        clientInfo = new ClientInfo(unsub.getDestination(),
                                Short.valueOf(unsub.getClientId()),
                                unsub.getFilter());
                        MDC.put("destination", clientInfo.getDestination());
                        embeddedServer.unsubscribe(clientInfo);
                        stopInstanceIfNecessary(clientInfo);// 尝试关闭
                        byte[] ackBytes = Nettys.ackPacket();
                        Nettys.write(channelHandlerContext.getChannel(), ackBytes, new ChannelFutureAggregator(unsub.getDestination(),
                                unsub, packet.getType(), ackBytes.length, System.nanoTime() - start));
                    } else
                    {
                        byte[] errorBytes = Nettys.errorPacket(401, MessageFormatter.format("destination or clientId is null", unsub.toString()).getMessage());
                        Nettys.write(channelHandlerContext.getChannel(), errorBytes, new ChannelFutureAggregator(unsub.getDestination(),
                                unsub, packet.getType(), errorBytes.length, System.nanoTime() - start, (short) 401));
                    }
                    break;
                case get:
                    Get get = Get.parseFrom(packet.getBody());
                    if (StringUtils.isNotEmpty(get.getDestination()) && StringUtils.isNotEmpty(get.getClientId()))
                    {
                        clientInfo = new ClientInfo(get.getDestination(), Short.valueOf(get.getClientId()));
                        MDC.put("destination", clientInfo.getDestination());
                        Message message = null;

                        // if (getWithAutoAck.getAutoAck()) {
                        // if (getWithAutoAck.getTimeout() == -1) {//是否是初始值
                        // message = embeddedServer.getWithAutoAck(clientInfo,
                        // getWithAutoAck.getFetchSize());
                        // } else {
                        // TimeUnit unit = convertTimeUnit(getWithAutoAck.getUnit());
                        // message = embeddedServer.getWithAutoAck(clientInfo,
                        // getWithAutoAck.getFetchSize(), getWithAutoAck.getTimeout(), unit);
                        // }
                        // } else {
                        if (get.getTimeout() == -1)
                        {// 是否是初始值
                            message = embeddedServer.getWithoutAck(clientInfo, get.getFetchSize());
                        } else
                        {
                            TimeUnit unit = convertTimeUnit(get.getUnit());
                            message = embeddedServer.getWithoutAck(clientInfo,
                                    get.getFetchSize(),
                                    get.getTimeout(),
                                    unit);
                        }
                        // }

                        if (message.getId() != -1 && message.isRaw())
                        {
                            List<ByteString> rowEntries = message.getRawEntryList();
                            // message size
                            int messageSize = 0;
                            messageSize += com.google.protobuf.CodedOutputStream.computeInt64Size(1, message.getId());

                            int dataSize = 0;
                            for (int i = 0; i < rowEntries.size(); i++)
                            {
                                dataSize += com.google.protobuf.CodedOutputStream.computeBytesSizeNoTag(rowEntries.get(i));
                            }
                            messageSize += dataSize;
                            messageSize += 1 * rowEntries.size();
                            // packet size
                            int size = 0;
                            size += com.google.protobuf.CodedOutputStream.computeEnumSize(3,
                                    PacketType.messages.getNumber());
                            size += com.google.protobuf.CodedOutputStream.computeTagSize(5)
                                    + com.google.protobuf.CodedOutputStream.computeRawVarint32Size(messageSize)
                                    + messageSize;
                            // recyle bytes
                            // ByteBuffer byteBuffer = (ByteBuffer)
                            // ctx.getAttachment();
                            // if (byteBuffer != null && size <=
                            // byteBuffer.capacity()) {
                            // byteBuffer.clear();
                            // } else {
                            // byteBuffer =
                            // ByteBuffer.allocate(size).order(ByteOrder.BIG_ENDIAN);
                            // ctx.setAttachment(byteBuffer);
                            // }
                            // CodedOutputStream output =
                            // CodedOutputStream.newInstance(byteBuffer);
                            byte[] body = new byte[size];
                            CodedOutputStream output = CodedOutputStream.newInstance(body);
                            output.writeEnum(3, PacketType.messages.getNumber());

                            output.writeTag(5, WireFormat.WIRETYPE_LENGTH_DELIMITED);
                            output.writeRawVarint32(messageSize);
                            // message
                            output.writeInt64(1, message.getId());
                            for (int i = 0; i < rowEntries.size(); i++)
                            {
                                output.writeBytes(2, rowEntries.get(i));
                            }
                            output.checkNoSpaceLeft();
                            Nettys.write(channelHandlerContext.getChannel(), body, new ChannelFutureAggregator(get.getDestination(),
                                    get, packet.getType(), body.length, System.nanoTime() - start, message.getId() == -1));

                            // output.flush();
                            // byteBuffer.flip();
                            // Nettys.write(ctx.getSocketChannel(), byteBuffer,
                            // null);
                        } else
                        {
                            Packet.Builder packetBuilder = Packet.newBuilder();
                            packetBuilder.setType(PacketType.messages);

                            Messages.Builder messageBuilder = Messages.newBuilder();
                            messageBuilder.setBatchId(message.getId());
                            if (message.getId() != -1)
                            {
                                if (message.isRaw() && !CollectionUtils.isEmpty(message.getRawEntryList()))
                                {
                                    messageBuilder.addAllMessages(message.getRawEntryList());
                                } else if (!CollectionUtils.isEmpty(message.getEntryList()))
                                {
                                    for (Entry entry : message.getEntryList())
                                    {
                                        messageBuilder.addMessages(entry.toByteString());
                                    }
                                }
                            }
                            byte[] body = packetBuilder.setBody(messageBuilder.build().toByteString()).build().toByteArray();
                            Nettys.write(channelHandlerContext.getChannel(), body, new ChannelFutureAggregator(get.getDestination(),
                                    get, packet.getType(), body.length, System.nanoTime() - start, message.getId() == -1));// 输出数据
                        }
                    } else
                    {
                        byte[] errorBytes = Nettys.errorPacket(401, MessageFormatter.format("destination or clientId is null", get.toString()).getMessage());
                        Nettys.write(channelHandlerContext.getChannel(), errorBytes, new ChannelFutureAggregator(get.getDestination(),
                                get, packet.getType(), errorBytes.length, System.nanoTime() - start, (short) 401));
                    }
                    break;
                case clientack:
                    ClientAck ack = ClientAck.parseFrom(packet.getBody());
                    MDC.put("destination", ack.getDestination());
                    if (StringUtils.isNotEmpty(ack.getDestination()) && StringUtils.isNotEmpty(ack.getClientId()))
                    {
                        if (ack.getBatchId() == 0L)
                        {
                            byte[] errorBytes = Nettys.errorPacket(402, MessageFormatter.format("batchId should assign value", ack.toString()).getMessage());
                            Nettys.write(channelHandlerContext.getChannel(), errorBytes, new ChannelFutureAggregator(ack.getDestination(),
                                    ack, packet.getType(), errorBytes.length, System.nanoTime() - start, (short) 402));
                        } else if (ack.getBatchId() == -1L)
                        { // -1代表上一次get没有数据，直接忽略之
                            // donothing
                        } else
                        {
                            clientInfo = new ClientInfo(ack.getDestination(), Short.valueOf(ack.getClientId()));
                            embeddedServer.ack(clientInfo, ack.getBatchId());
                            new ChannelFutureAggregator(ack.getDestination(), ack, packet.getType(), 0, System.nanoTime() - start).operationComplete(null);
                        }
                    } else
                    {
                        byte[] errorBytes = Nettys.errorPacket(401, MessageFormatter.format("destination or clientId is null", ack.toString()).getMessage());
                        Nettys.write(channelHandlerContext.getChannel(), errorBytes, new ChannelFutureAggregator(ack.getDestination(),
                                ack, packet.getType(), errorBytes.length, System.nanoTime() - start, (short) 401));
                    }
                    break;
                case clientrollback:
                    ClientRollback rollback = ClientRollback.parseFrom(packet.getBody());
                    MDC.put("destination", rollback.getDestination());
                    if (StringUtils.isNotEmpty(rollback.getDestination())
                            && StringUtils.isNotEmpty(rollback.getClientId()))
                    {
                        clientInfo = new ClientInfo(rollback.getDestination(),
                                Short.valueOf(rollback.getClientId()));
                        if (rollback.getBatchId() == 0L)
                        {
                            embeddedServer.rollback(clientInfo);// 回滚所有批次
                        } else
                        {
                            embeddedServer.rollback(clientInfo, rollback.getBatchId()); // 只回滚单个批次
                        }
                        new ChannelFutureAggregator(rollback.getDestination(), rollback, packet.getType(), 0, System.nanoTime() - start).operationComplete(null);
                    } else
                    {
                        byte[] errorBytes = Nettys.errorPacket(401, MessageFormatter.format("destination or clientId is null", rollback.toString()).getMessage());
                        Nettys.write(channelHandlerContext.getChannel(), errorBytes, new ChannelFutureAggregator(rollback.getDestination(),
                                rollback, packet.getType(), errorBytes.length, System.nanoTime() - start, (short) 401));
                    }
                    break;
                default:
                    byte[] errorBytes = Nettys.errorPacket(400, MessageFormatter.format("packet type={} is NOT supported!", packet.getType()).getMessage());
                    Nettys.write(channelHandlerContext.getChannel(), errorBytes, new ChannelFutureAggregator(channelHandlerContext.getChannel().getRemoteAddress().toString(),
                            null, packet.getType(), errorBytes.length, System.nanoTime() - start, (short) 400));
                    break;
            }
        } catch (Throwable exception)
        {
            byte[] errorBytes = Nettys.errorPacket(400, MessageFormatter.format("something goes wrong with channel:{}, binlogParseException={}",
                    channelHandlerContext.getChannel(),
                    ExceptionUtils.getStackTrace(exception)).getMessage());
            Nettys.write(channelHandlerContext.getChannel(), errorBytes, new ChannelFutureAggregator(channelHandlerContext.getChannel().getRemoteAddress().toString(),
                    null, packet.getType(), errorBytes.length, System.nanoTime() - start, (short) 400));
        } finally
        {
            MDC.remove("destination");
        }
    }

    public void exceptionCaught(ChannelHandlerContext channelHandlerContext, ExceptionEvent exceptionEvent) throws Exception
    {
        logger.error("something goes wrong with channel:{}, binlogParseException={}",
                channelHandlerContext.getChannel(),
                ExceptionUtils.getStackTrace(exceptionEvent.getCause()));

        channelHandlerContext.getChannel().close();
    }

    public void channelClosed(ChannelHandlerContext channelHandlerContext, ChannelStateEvent channelStateEvent) throws Exception
    {
        // logger.info("remove binding subscription value object if any...");
        // ClientInfo clientIdentity = (ClientInfo) ctx.getAttachment();
        // // 如果唯一的订阅者都取消了订阅，直接关闭服务，针对内部版本模式下可以减少资源浪费
        // if (clientIdentity != null) {
        // stopInstanceIfNecessary(clientIdentity);
        // }
    }

    private void stopInstanceIfNecessary(ClientInfo clientInfo)
    {
        List<ClientInfo> clientInfoList = embeddedServer.getClientInfoList(clientInfo.getDestination());
        if (clientInfoList != null && clientInfoList.size() == 1 && clientInfoList.contains(clientInfo))
        {
            Monitor runningMonitor = ServerRunningMonitors.getRunningMonitor(clientInfo.getDestination());
            if (runningMonitor.isRunning())
            {
                runningMonitor.release();
            }
        }
    }

    private TimeUnit convertTimeUnit(int unit)
    {
        switch (unit)
        {
            case 0:
                return TimeUnit.NANOSECONDS;
            case 1:
                return TimeUnit.MICROSECONDS;
            case 2:
                return TimeUnit.MILLISECONDS;
            case 3:
                return TimeUnit.SECONDS;
            case 4:
                return TimeUnit.MINUTES;
            case 5:
                return TimeUnit.HOURS;
            case 6:
                return TimeUnit.DAYS;
            default:
                return TimeUnit.MILLISECONDS;
        }
    }

    public void setEmbeddedServer(EmbeddedServer embeddedServer)
    {
        this.embeddedServer = embeddedServer;
    }

}
